package com.loanafter.fox.monitor.logback.connection;

import com.loanafter.fox.monitor.logback.event.Event;
import com.loanafter.fox.monitor.logback.util.JsonMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

/**
 * @author jasonli@kuainiugroup.com
 * @date 2020/7/30 下午6:05
 */
public class HttpConnection extends AbstractConnection {
    private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class);
    private static final Charset UTF_8 = Charset.forName("UTF-8");
    /**
     * URL of the Sentry endpoint.
     */
    private final String monitorUrl;

    /**
     * Default timeout of an HTTP connection to Sentry.
     */
    private static final int DEFAULT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(1);
    /**
     * Timeout of an HTTP connection to Sentry.
     */
    private int timeout = DEFAULT_TIMEOUT;

    /**
     * Creates an HTTP connection to a Fox monitor server.
     *
     * @param monitorUrl URL to the Fox monitor API.
     */
    public HttpConnection(String monitorUrl) {
        this.monitorUrl = monitorUrl;
    }

    @Override
    protected void doSend(Event event) throws ConnectionException {
        HttpURLConnection connection = getConnection();
        try {
            connection.connect();
            DataOutputStream outputStream = new
                    DataOutputStream(connection.getOutputStream());
            String json = URLEncoder.encode(JsonMapper.toJsonString(event), UTF_8.name());
            outputStream.writeBytes(json);
            outputStream.close();
            connection.getInputStream().close();
        } catch (IOException e) {
            String errorMessage = null;
            final InputStream errorStream = connection.getErrorStream();
            if (errorStream != null) {
                errorMessage = getErrorMessageFromStream(errorStream);
            }
            if (null == errorMessage || errorMessage.isEmpty()) {
                errorMessage = "An exception occurred while submitting the event to the Fox monitor server.";
            }

            Long retryAfterMs = null;
            String retryAfterHeader = connection.getHeaderField("Retry-After");
            if (retryAfterHeader != null) {
                // CHECKSTYLE.OFF: EmptyCatchBlock
                try {
                    // CHECKSTYLE.OFF: MagicNumber
                    retryAfterMs = Long.parseLong(retryAfterHeader) * 1000L; // seconds -> milliseconds
                    // CHECKSTYLE.ON: MagicNumber
                } catch (NumberFormatException nfe) {
                    // noop, use default retry
                }
                // CHECKSTYLE.ON: EmptyCatchBlock
            }

            throw new ConnectionException(errorMessage, e, retryAfterMs);
        } finally {
            connection.disconnect();
        }
    }

    /**
     * Opens a connection to the Sentry API allowing to send new events.
     *
     * @return an HTTP connection to Sentry.
     */
    protected HttpURLConnection getConnection() {
        try {
            HttpURLConnection connection;
            connection = (HttpURLConnection) new URL(monitorUrl).openConnection();

            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setConnectTimeout(timeout);
            connection.setReadTimeout(timeout);
            connection.setRequestProperty("Content-Type", "application/json");
            return connection;
        } catch (Exception e) {
            throw new IllegalStateException("Couldn't set up a connection to the Fox monitor server.", e);
        }
    }

    private String getErrorMessageFromStream(InputStream errorStream) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream, UTF_8));
        StringBuilder sb = new StringBuilder();
        try {
            String line;
            // ensure we do not add "\n" to the last line
            boolean first = true;
            while ((line = reader.readLine()) != null) {
                if (!first) {
                    sb.append("\n");
                }
                sb.append(line);
                first = false;
            }
        } catch (Exception e2) {
            logger.error("Exception while reading the error message from the connection.", e2);
        }
        return sb.toString();
    }

}
